using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Routing;
using System.Web.UI;
using ManagedUIFlow.Infrastructure.Services.Helpers;
using ManagedUIFlow.Model;

namespace ManagedUIFlow.Infrastructure.Services
{
    public class ContinuationSessionFlowViewService : IFlowViewService
    {
        public const int RepositoryQueueLimit = 40;
        public const string SessionSerializerServiceQueue = "ContinuationSerializerServiceQueue";
        public const string RedirectTicketKey = "PostRedirectGetServiceTicket";
        public const string RedirectUrlKey = "PostRedirectGetServiceUrl";

        private FlowService _flowService;

        public FlowService FlowService
        {
            get
            {
                return _flowService ?? FlowService.Current;
            }
            set { _flowService = value; }
        }

        public string Serialize(ManagedFlow flow, RequestContext requestContext)
        {
            var newKey = Guid.NewGuid();
            EnqueueFlow(newKey, flow, requestContext);                       
            return newKey.ToString();
        }       

        public ManagedFlow Deserialize(string key, RequestContext requestContext)
        {
            if (IsRedirectBack(requestContext))
                return requestContext.HttpContext.Session[RedirectTicketKey] as ManagedFlow;

            var flow = DequeueFlow(key, requestContext);
            if (flow != null)
            {
                var newNavigation = FlowService.GetNavigationFor(flow.Name, flow.CurrentNavigation,
                                                                 requestContext.GetState());
                flow.SetNavigation(newNavigation);
                return flow;
            }

            throw new FlowException(
                     "Either you have used browser back/refresh button as many times or continuation store limit exceeded.");
        }

        public void PostRedirectGet(string key, ManagedFlow flow, RequestContext context)
        {
            if (IsRedirectBack(context))
            {
                context.HttpContext.Session.Remove(RedirectTicketKey);
                return;
            }
            context.HttpContext.Session.Add(RedirectTicketKey, flow);
            var redirectUrl = !string.IsNullOrEmpty(flow.RedirectUrl)
                                  ? flow.RedirectUrl
                                  : flow.Name;
            context.RouteData.Values["controller"] = key;
            context.HttpContext.Response.Redirect(UrlHelper.GenerateUrl(null, redirectUrl, null,
                                                                      new RouteValueDictionary(),
                                                                      RouteTable.Routes, context, false
                /* includeImplicitMvcValues */));            
        }

        public void Remove(ManagedFlow flow, RequestContext requestContext)
        {
            var queueName = string.Format("{0}_{1}", SessionSerializerServiceQueue, flow.Key);
            requestContext.HttpContext.Session.Remove(queueName);
        }

        private static ManagedFlow DequeueFlow(string key, RequestContext requestContext)
        {
            var queue =
                requestContext.HttpContext.Session[SessionSerializerServiceQueue] as
                Dictionary<string, Dictionary<string, string>>;
            if(queue != null)
            {
                var flows = queue.Values.Select(v => v).ToList();
                var entry = flows.Find(f => f.Keys.Contains(key));
                if (entry != null)
                {
                    var flowData = entry[key];
                    var formatter = new LosFormatter();
                    return formatter.Deserialize(flowData) as ManagedFlow;
                }                    
            }

            return null;
        }        

        private static bool IsRedirectBack(RequestContext requestContext)
        {
            return requestContext.HttpContext.Session[RedirectTicketKey] != null;
        }

        private static void EnqueueFlow(Guid key, ManagedFlow flow, RequestContext requestContext)
        {
            var formatter = new LosFormatter();
            var strBuilder = new StringBuilder();
            var strWriter = new StringWriter(strBuilder);
            formatter.Serialize(strWriter, flow);

            var queue =
                requestContext.HttpContext.Session[SessionSerializerServiceQueue] as
                Dictionary<string, Dictionary<string, string>>;

            if (queue == null)
                queue = new Dictionary<string, Dictionary<string, string>> { { flow.Key.ToString(), new Dictionary<string, string>() { { key.ToString(), strBuilder.ToString()} } } };
            else
            {
                if(queue.ContainsKey(flow.Key.ToString()))
                {
                    var existingFlows = queue[flow.Key.ToString()];
                    if (existingFlows.Count >= RepositoryQueueLimit)
                    {
                        var flowKey = existingFlows.Keys.First();
                        existingFlows.Remove(flowKey);
                    }
                    existingFlows.Add(key.ToString(), strBuilder.ToString());                        
                }                
                else
                    queue.Add(flow.Key.ToString(), new Dictionary<string, string> { { key.ToString(), strBuilder.ToString()} });
            }

            requestContext.HttpContext.Session[SessionSerializerServiceQueue] = queue;
        }
    }
}